
package nsalt

import chisel3._
import chisel3.util._

import nsalt.fetch._
import nsalt.decode._
import nsalt.bus._
import nsalt.mem._
import nsalt.util._

// Used in both Execute Stage and various caches.
object AddressSpace extends Constants {
  // address out of MMIO will be considered as DRAM
  def mmio = List(
  //(start,       size        )
    (0x30000000L, 0x10000000L ),  // internal devices, such as CLINT and PLIC
    (MMIO_BASE,   MMIO_SIZE   )   // external devices
  )

  def isMMIO(addr: UInt) = mmio
    .map(range => {
      // require(isPow2(range._2))
      val bits = log2Up(range._2)
      (addr ^ range._1.U)(PHYS_ADDR_LEN - 1, bits) === 0.U
    }).reduce(_ || _)
}

class NutCore extends Module with Constants {

  val io = IO(new Bundle {
    val imem = new BusCached
    val dmem = new BusCached
    val mmio = new BusUncached
    val frontend = Flipped(new BusUncached)
  })

  // The Line of Modules
  // ==========================================================================
  // Front-end & Back-end are momentarily removed, with all func units exposed 
  // in a seq, and then connect them with Pipe module.

    // conventional Front-End
    val fetch     = Module(new Fetch)        // 0
    val decode    = Module(new Decode)       // 1

    // conventional Back-End
    val issue     = Module(new Issue)        // 2
    val execute   = Module(new Execute)      // 3
    val writeback = Module(new WriteBack)    // 4

  // Connect Modules Successively
  // ==========================================================================
  // Note that we didn't use PipelineVector2Connect since dual decoder / issuer
  // is not used yet. We will extend the Pipe module for it later.
  // 
  // Flush signal for each pipeline stage is distributed to func units directly.

  val flushPipe = fetch.io.flushPipe

  PipeLink( fetch.io.out,   decode.io.in,    fetch.io.out.fire,   flushPipe(0) )
  PipeLink( decode.io.out,  issue.io.in,     decode.io.out.fire,  flushPipe(1) )
  PipeLink( issue.io.out,   execute.io.in,   issue.io.out.fire,   flushPipe(2) )
  PipeLink( execute.io.out, writeback.io.in, execute.io.out.fire, flushPipe(3) )

  // issue <> writeback 
  issue.io.writeback <> writeback.io.writeReg

  // redirection is connected between fetch and writeback
  // https://github.com/OSCPU/NutShell/blob/fd86beadfc47f52973270ce6109edebd2a30363b/src/main/scala/nutcore/NutCore.scala#L112
  fetch.io.redirect <> writeback.io.redirect

  // forward is connected between issue and execute
  issue.io.forward <> execute.io.forward  

  // Usage of Dummy TLB & Cache:
  // https://github.com/OSCPU/NutShell/blob/fd86beadfc47f52973270ce6109edebd2a30363b/src/main/scala/nutcore/NutCore.scala#L116
  // ==========================================================================
  // In the embedded version, both TLB and Cache are disabled, and dummy TLB and
  // Cache are used.
  // 
  // Dummy TLB behaves like a passthrough, without internal logic.
  // 
  // Dummy Cache contains register that holds the current word being transmitted.
  // 
  // Check implementation of TLB and Cache for detail.

  val mmioXbar = Module(new BusCrossbarFrom(2))
  val dmemXbar = Module(new BusCrossbarFrom(4))

  // TLB configuration for instruction mem
  val transInstrConf = TransLookasideConf(
    NAME        = "transInstr", 
    USER_BITS   = ICACHE_USER_BUNDLE_WIDTH, 
    TOTAL_ENTRY = 4
  )

  // instruction TLB
  val transInstr = TransLookaside(
    in     = fetch.io.imem, 
    mem    = dmemXbar.io.in(1), 
    flush  = fetch.io.flushPipe(0) | fetch.io.flushPred, 
    csrMMU = execute.io.memMMU.imem,
    // enable = HasITLB
    // enable = false.B
  )(transInstrConf)

  fetch.io.ipf := transInstr.io.ipf

  // Cache configuration for instruction mem
  val cacheInstrConf = CacheConf(
    ro = true, 
    name = "icache", 
    userBits = ICACHE_USER_BUNDLE_WIDTH
  )

  // instruction cache
  val instrCache = CacheInstr(
    in     = transInstr.io.out, 
    mmio   = mmioXbar.io.in.take(1), 
    flush  = Fill(2, fetch.io.flushPipe(0) | fetch.io.flushPred), 
    empty  = transInstr.io.cacheEmpty, 
    // enable = HasIcache
    // enable = true.B
  )(cacheInstrConf)

  io.imem <> instrCache
  
  // data TLB configuration
  val transDataConf = TransLookasideConf(
    NAME = "transData", 
    TOTAL_ENTRY = 64
  )

  // data TLB
  val transData = TransLookaside(
    in     = execute.io.dmem, 
    mem    = dmemXbar.io.in(2), 
    flush  = false.B, 
    csrMMU = execute.io.memMMU.dmem, 
    // enable = HasDTLB
    // enable = false.B
  )(transDataConf)

  dmemXbar.io.in(0) <> transData.io.out

  val cacheDataConf = CacheConf(
    ro = false,
    name = "dcache"
  )

  val cacheData = CacheData(
    in     = dmemXbar.io.out, 
    mmio   = mmioXbar.io.in.drop(1), 
    flush  = "b00".U, 
    empty  = transInstr.io.cacheEmpty, 
    // enable = HasDcache
    // enable = false.B
  )(cacheDataConf)
  io.dmem <> cacheData

  // Make DMA access through L1 DCache to keep coherence
  dmemXbar.io.in(3) <> io.frontend

  io.mmio <> mmioXbar.io.out

}
